package com.onpositive.knowledge.model.impl;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.knowledge.model.value.audits.AbstractAuditResult;
import com.onpositive.knowledge.model.value.audits.GeneralAuditResult;
import com.onpositive.knowledge.model.value.audits.RangeAuditResult;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.IHasValue;
import com.onpositive.knowledge.model.values.ValueTraverser;
import com.onpositive.semantic.model.api.meta.IMeta;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.units.ParsedDate;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedFileList;
import com.onpositive.units.ParsedIdent;
import com.onpositive.units.ParsedLinkArray;
import com.onpositive.units.ParsedScalar;
import com.onpositive.units.ParsedSection;
import com.onpositive.units.ParsedString;
import com.onpositive.units.ParsedTerm;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.ParsedValueSequence;
import com.onpositive.units.ParsedValueSequenceV2;
import com.onpositive.units.RangeValue;
import com.onpositive.units.TextContent;
import com.onpositive.units.ValueCluster;

public class MergingProperty implements IProperty
{
	/**
	 * 
	 */
	private static final long serialVersionUID = -2353985295523039185L;

	private static LinkedHashMap<String, Integer> valueRankMap ;
	static{		
		valueRankMap = new LinkedHashMap<String, Integer>() ;
		valueRankMap.put( TextContent.ID, 100500 ) ;
		valueRankMap.put( ParsedDimension.ID, 10000 ) ;
		valueRankMap.put( ParsedDate.ID, 9000 ) ;
		valueRankMap.put( ParsedScalar.ID, 8000 ) ;
		valueRankMap.put( ParsedValueSequence.ID, 100 ) ;
		valueRankMap.put( ParsedIdent.ID, 40 ) ;
		valueRankMap.put( ParsedString.ID, 30 ) ;
		
		valueRankMap.put( ParsedSection.ID, -1   ) ;
		valueRankMap.put( ParsedLinkArray.ID, -9000   ) ;
		valueRankMap.put( ParsedFileList.ID,  -100500 ) ;
	}
	
	private static final int INITIAL_INFERED_VALUE = 27 ;
	
	MergingPropertyData data = new MergingPropertyData();
	final private AbstractAuditResult mainAuditResult ;
	
	public MergingProperty( IProperty prop, AbstractAuditResult result )
	{
		this.data.prop = prop ;
		this.mainAuditResult = result ;
	}

	public MergingProperty( IProperty prop )
	{
		this.data.prop = prop ;
		this.mainAuditResult = null ;
	}

	@Override
	public IMeta getMeta() {
		return data.prop.getMeta() ;
	}

	@Override
	public String getId() {

		return data.prop.getId() ;
	}

	

	@Override
	public Object getValue(Object obj) {
		
		Object val0 = data.prop.getValue(obj) ;
		if( val0 == null || !(val0 instanceof DescribedValue) )
			return null ;
		
		DescribedValue dv = (DescribedValue) val0 ;
		
		Object val1 = dv.value() ;
		if(  val1 instanceof String )
			return val1 ;
		else if( val1 instanceof Boolean )
			return ((Boolean)val1).toString() ;
		
		ParsedValue pv = prepareValue( val1 ) ;
		return pv ;
	}

	private ParsedValue prepareValue(Object val1)
	{
		String id = getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		
		if( id.equals( ParsedDimension.ID) ){
			return mergeRange( val1, ParsedDimension.class ) ;
		}
		if( id.equals( ParsedScalar.ID) ){
			return mergeRange( val1, ParsedScalar.class ) ;	
		}
		if( id.equals( ParsedDate.ID) ){
			return mergeRange( val1, ParsedDate.class ) ;
		}
		if( id.equals( TextContent.ID) ){
			return chooseSingle( val1, TextContent.class ) ;
		}
		if( id.equals( ParsedFileList.ID) ){
			return chooseSingle( val1, ParsedFileList.class ) ;
		}
		if( id.equals( ParsedLinkArray.ID) ){
			return chooseSingle( val1, ParsedLinkArray.class ) ;
		}
		
		Boolean isIdent = getMeta().getSingleValue( IPropertyConstants.CHAR_IS_IDENTIFICATOR, Boolean.class, null ) ;
		
		if( id == ParsedIdent.ID || (isIdent != null && isIdent) ){
			ParsedValue pv = chooseParticularClass( val1, ParsedIdent.class ) ;
			if( pv != null ){
				if( pv instanceof ParsedValueSequenceV2 )
					return pv ;
				else
					return new ParsedValueSequenceV2( new ParsedValue[]{ pv }, null, null ) ;					
			}
			else
				return new ParsedValueSequenceV2( new ParsedValue[]{ mergeAll( val1 ) }, null, null ) ;
		}		
		return mergeAll( val1 ) ;
	}	

	private ParsedValue chooseSingle( Object val, Class<? extends ParsedValue> clazz )
	{		
		return doChoose( val, clazz, false, true );	
	}

	private ParsedValue chooseParticularClass( Object val, Class<? extends ParsedValue> clazz )
	{
		return doChoose( val, clazz, false, false );	
	}	
	
	private ParsedValue mergeRange( Object val, Class<? extends RangeValue<?>> clazz )
	{
		return doChoose( val, clazz, true, false );	
	}
	
	private ParsedValue doChoose(Object val, Class<? extends ParsedValue> clazz, boolean mergeRange, boolean chooseSingle )
	{
		if( clazz.isInstance(val) )
			return createSimpleValue(val, clazz, null);
		
		if( val instanceof ValueCluster<?> ){
			ValueCluster<?> vc = (ValueCluster<?>) val ;
			if( vc.getTargetClass() == clazz )				
				return processCluster(vc,clazz);
		}
		
		ParsedValue[] valArr = extractTargetValues(val, clazz);
		String comment = extractStringValue( val );
		
		if( valArr.length == 0 )
			return null ;
		
		if( valArr.length == 1 )
			return createSimpleValue(valArr[0], clazz, comment) ;
		
		if( mergeRange ){
			Class<? extends RangeValue> rangeClass = isRangeValueSubclass( clazz );
			
			
			RangeValue<?> rg = RangeValue.mergeRange( valArr, rangeClass, comment);
			ParsedValue result = rg ;
			
			if( rangeClass != ParsedDate.class && rg.isRange() && rg.getValueClass() == Double.class )
			{
				double epsilon1 = chooseEpsilon( valArr ) ;
				double epsilon2 = chooseEpsilon_5prcent( valArr ) ;
				double epsilon = Math.max(epsilon1, epsilon2) ;
				
				RangeValue<Double> dRange = (RangeValue<Double>) rg ;
				if( dRange.getUpperBound()-dRange.getLowerBound() < epsilon ){
					result = (ParsedValue) dRange.clone( dRange.getUpperBound(), comment ) ; 
				}
			}			
			return result ;
		}
		if( chooseSingle )
			return valArr[0] ;
		
		return new ParsedValueSequenceV2( valArr, comment, null );
	}

	private double chooseEpsilon(ParsedValue[] valArr)
	{
		List<RangeAuditResult> auditResultList = null ;
		
		if( this.mainAuditResult instanceof RangeAuditResult ){
			auditResultList = new ArrayList<RangeAuditResult>() ;
			auditResultList.add( (RangeAuditResult) this.mainAuditResult ) ;
		}		
		else if( this.mainAuditResult instanceof GeneralAuditResult )
		{			
			GeneralAuditResult gResult = (GeneralAuditResult) this.mainAuditResult ;
			auditResultList = gResult.getResults( this.data.prop.getId(), null, RangeAuditResult.class ) ;
		}
		
		if( auditResultList == null || auditResultList.size() == 0 )
			return Double.MIN_VALUE ;
		
		double[] epsilonArray = auditResultList.iterator().next().getEpsilonArray() ;
		if( epsilonArray == null )
			return Double.MIN_VALUE ;
		
		ArrayList<Double> values = new ArrayList<Double>() ;
		ArrayList<Double> epLst = new ArrayList<Double>() ;
		
		for( ParsedValue pv : valArr ){
			
			values.clear() ;
			
			Object valObj = pv.value() ;
			if( valObj == null )
				continue ;
			
			if( valObj instanceof Object[] )
			{
				Class<?> componentType = valObj.getClass().getComponentType();
				if( componentType == Double.class ){
					Double[] dArr = (Double[]) valObj ;
					int l = Math.min(dArr.length, 2) ;
					for( int i = 0 ; i < l ; i++ )
						values.add(dArr[i]) ;
				}
				else if( componentType == double.class ){
					double[] dArr = (double[]) valObj ;
					int l = Math.min(dArr.length, 2) ;
					for( int i = 0 ; i < l ; i++ )
						values.add(dArr[i]) ;
				}
				else
					return Double.MIN_VALUE ; 
			}
			else if( valObj instanceof Number )
			{				
				Number num = (Number) valObj ;
				values.add( num.doubleValue() ) ;				
			}
			else
				return Double.MIN_VALUE ;
			
			int l = epsilonArray.length ;
			if( l < 4 )
				return epsilonArray[1] ;
			
			for( Double val : values ){
				
				for( int i = 2 ; i < l ; i+=2 )
				{
					double dif0 = Math.abs( val - epsilonArray[i-2] ) ;
					double dif1 = Math.abs( val - epsilonArray[ i ]  ) ;
					
					if( dif1 >= dif0 ){
						double curEps = epsilonArray[i-1];
						epLst.add(curEps) ;
						break ;
					}					
				}				
			}			
		}
		
		double e = 0 ;
		for( Double d : epLst )
			e += d ;
		
		e /= epLst.size() ;		
		return e;
	}
	
	private double chooseEpsilon_5prcent(ParsedValue[] valArr)
	{
		double maxValue = Double.MIN_VALUE ;
		double minValue = Double.MAX_VALUE ;
		boolean gotChange = false ;
		for( ParsedValue pv : valArr ){
			
			Object valObj = pv.value() ;
			if( valObj == null )
				continue ;
			
			if( valObj instanceof Object[] )
			{
				Class<?> componentType = valObj.getClass().getComponentType();
				if( componentType == Double.class ){
					Double[] dArr = (Double[]) valObj ;
					int l = Math.min(dArr.length, 2) ;
					for( int i = 0 ; i < l ; i++ ){
						maxValue = Math.max(maxValue, dArr[i] ) ;
						minValue = Math.min(minValue, dArr[i] ) ;
						gotChange = true ;
					}
				}
				else if( componentType == double.class ){
					double[] dArr = (double[]) valObj ;
					int l = Math.min(dArr.length, 2) ;
					for( int i = 0 ; i < l ; i++ ){
						maxValue = Math.max(maxValue, dArr[i] ) ;
						minValue = Math.min(minValue, dArr[i] ) ;
						gotChange = true ;
					}
				}				
			}
			else if( valObj instanceof Number )
			{				
				Number num = (Number) valObj ;
				double dVal = num.doubleValue();
				maxValue = Math.max(maxValue, dVal ) ;
				minValue = Math.min(minValue, dVal ) ;
				gotChange = true ;
			}
		}
		
		if( !gotChange )
			return Double.MIN_VALUE ;
		
		if( minValue < 0 && maxValue >= 0 )
			maxValue -= minValue ;
		
		else if( minValue < 0 && maxValue < 0 )
			maxValue = -minValue ;
		
		double e = 0.05 * maxValue ;		
		return e;
	}

	private ParsedValue processCluster(ValueCluster<?> vc, Class<? extends ParsedValue> clazz)
	{
		ParsedValue targetValue = vc.getTargetValue() ;
		String comment ;
		ParsedValue[] valArr = vc.getValuesArray() ;
		int l = valArr.length ;
		if( l == 1 )
			return targetValue ;
		
		if( targetValue == valArr[0] ){
			StringBuilder bld = new StringBuilder() ;
			for( int i = 1 ; i < l ; i++ ){
				bld.append( valArr[i].getValueAsString() ) ;
				bld.append( "; " ) ;
			}
			comment = bld.substring(0, bld.length()-2) ;
		}
		else if ( targetValue == valArr[ l-1] ){
			StringBuilder bld = new StringBuilder() ;
			for( int i = 0 ; i < l-1 ; i++ ){
				bld.append( valArr[i].getValueAsString() ) ;
				bld.append( "; " ) ;
			}
			comment = bld.substring(0, bld.length()-2) ;
		}
		else
			comment = vc.getValueAsString() ;		
		
		ParsedValue pv = createSimpleValue(targetValue, clazz, comment) ;
		return pv ;
	}
	
	private ParsedValue mergeAll( Object val ) {
		
		String stringValue = extractStringValue(val);
		return new ParsedString( stringValue, null ) ;
	}
	
	private ParsedValue[] extractTargetValues(Object val, Class<? extends ParsedValue> clazz)
	{
		LinkedHashSet<ParsedValue> targetValueSet = new LinkedHashSet<ParsedValue>() ;		
		ClassExtractor vt = getValueTraverser( clazz ) ;
		vt.setSet(targetValueSet) ;
		vt.iterateValues( null, val, false, null, 0 ) ;
		
		ArrayList<ParsedValue> suspicious = null ;
		for( ParsedValue pv : targetValueSet ){
			if( pv.getValidity() < 0 ){
				if ( suspicious == null )
					suspicious = new ArrayList<ParsedValue>() ;
				suspicious.add( pv);
			}
		}
		
		if( suspicious != null && suspicious.size() < targetValueSet.size() )		
			for( ParsedValue pv : suspicious )
				targetValueSet.remove(pv) ;
					
		ParsedValue[] valArr = targetValueSet.toArray( new ParsedValue[ targetValueSet.size()]) ;
		return valArr;
	}
	

	private String extractStringValue(Object val) {
		StringBuilder builder = new StringBuilder() ;
		stringValueExtractor.setBuilder(builder) ;
		stringValueExtractor.iterateValues(null, val, false, null, 0) ;
		
		int commentLength = builder.length();
		String comment = builder.length() > 2 ? builder.substring(0, commentLength-2) : builder.toString() ;
		return comment;
	}
	
	private ParsedValue createSimpleValue( Object val, Class<? extends ParsedValue> clazz, String comment)
	{
		try {
			return clazz.getConstructor( ParsedValue.class, String.class ).newInstance( val, comment ) ;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		return (ParsedValue) val ;
	}
	
	private Class<? extends RangeValue<?>> isRangeValueSubclass(Class<? extends ParsedValue> clazz) {
		
		for( Class<?> cl = clazz.getSuperclass() ; cl != Object.class ; cl=cl.getSuperclass() ){
			if( cl == RangeValue.class )
				return (Class<? extends RangeValue<?>>) clazz ;
		}
		return null;
	}
	
	private StringValueExtractor stringValueExtractor = new StringValueExtractor() ;
	private final HashMap<Class<? extends ParsedValue>, ClassExtractor> valueTraverserMap =
			new HashMap<Class<? extends ParsedValue>, ClassExtractor>() ;
	
	private ClassExtractor getValueTraverser(Class<? extends ParsedValue> clazz) {
		
		ClassExtractor ovt = valueTraverserMap.get(clazz) ;
		if( ovt == null )
		{
			ovt = new ClassExtractor(clazz ) ;
			valueTraverserMap.put(clazz, ovt) ;
		}
		return ovt;
	}

	private Object chooseValue(Collection<?> val1) {

		if( val1 == null || val1.size() == 0 )
			return null ;

//		if( val1.size() == 1 )
//			return val1.iterator().next() ;

		Object maxValue = null ;
		int maxRank = Integer.MIN_VALUE ;

		ParsedValueSequence mergedSequence = new ParsedValueSequence( "MergedProperty" ) ; 
		for( Object o : val1 )
		{
			mergeSequence( o, mergedSequence ) ;
			int rank = getRank( o ) ;
			if( rank > maxRank ){
				maxValue = o ;
				maxRank = rank ; 
			}
		}
		
		final Integer integer = valueRankMap.get( mergedSequence.getId() ) + INITIAL_INFERED_VALUE;
		if( integer < maxRank )			
			return maxValue ;
		else
			return mergedSequence ;
	}
	
	@SuppressWarnings("unchecked")
	private void mergeSequence(Object o, ParsedValueSequence mergedSequence) {
		
		Object v1 = o ;
		IHasValue v2 = null ;
		while( v1 instanceof IHasValue  )
		{
			v2 = (IHasValue) v1 ;
			v1 = v2.value() ;
		}
		
		if( !(v2 instanceof ParsedValue) )
			return ;
		
		if( v2 instanceof ParsedValueSequence )
		{
			LinkedHashSet<ParsedString> wholeSet = (LinkedHashSet<ParsedString>) mergedSequence.value() ;
			LinkedHashSet<ParsedString> set = (LinkedHashSet<ParsedString>) ((ParsedValueSequence)v2).value() ;
			
			for( ParsedString pStr : set )
				if( !wholeSet.contains(pStr) )
					wholeSet.add(pStr) ;			
		}		
	}

	private final int getRank( Object val )
	{
		
		Object v1 = val ;
		IHasValue v2 = null ;
		
		int infered = INITIAL_INFERED_VALUE ;
		while( v1 instanceof IHasValue  )
		{
			if( v1 instanceof DescribedValue )
			{
				byte locInf = ((DescribedValue)v1).getInferred() ;
				if( infered > locInf )
					infered = locInf;
			}
			v2 = (IHasValue) v1 ;
			v1 = v2.value() ;
		}		

		if( v2 instanceof ParsedValue )
			return infered + valueRankMap.get( ((ParsedValue)v2).getId() ) ;

		if ( v1 instanceof Number )
			return infered + valueRankMap.get( ParsedScalar.ID ) ;
		
		if ( v1 instanceof String )
			return infered + valueRankMap.get( ParsedString.ID ) ;
		
		return Integer.MIN_VALUE ;
		
	}
	
	//@Override
	public Object getValue_old(Object obj) {
		
		Object val0 = data.prop.getValue(obj) ;
		if( val0 == null || !(val0 instanceof DescribedValue) )
			return null ;
		
		DescribedValue dv = (DescribedValue) val0 ;
		
		Object val1 = dv.value() ;
		if( val1 instanceof Collection<?> )
			return chooseValue( (Collection<?>)val1 ) ;
		else
			return val1 ;
	}
	
	private static class StringValueExtractor extends ValueTraverser{

		private StringValueExtractor(){
			super( new Class<?>[]{ ParsedValue.class }, new Class<?>[]{ ParsedValue.class } ) ;
		}
		
		private StringBuilder builder ;
		
		@Override
		protected void process( IProperty prop, Object val, Map<String,Object> metaInfo, int index ) {
			if( val instanceof ParsedValue ){
				ParsedValue pv = (ParsedValue) val ;
				builder.append( pv.getContextFragmentString() ) ;
				builder.append("; ") ;
			}
		}

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return null;
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return null;
		}

		private void setBuilder(StringBuilder builder) {
			this.builder = builder;
		}
		
	}
	
	private static class ClassExtractor extends ValueTraverser{

		private ClassExtractor( Class<? extends ParsedValue> clazz ){
			super( new Class<?>[]{ clazz }, new Class<?>[]{ clazz } ) ;
		}
		
		private Collection<ParsedValue> set ;
		
		@Override
		protected void process( IProperty prop, Object val, Map<String,Object> metaInfo, int index ) {
			if( val instanceof ParsedValue )
				set.add((ParsedValue) val) ;			
		}

		@Override
		protected Class<?>[] createNecessaryClasses() {
			return null;
		}

		@Override
		protected Class<?>[] createTargetClasses() {
			return null;
		}

		private void setSet( Collection<ParsedValue> set) {
			this.set = set;
		}		
	}

}
